package com.job.common.utils;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;

import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;

/**
 * 方法耗时计时器
 *
 * @author sunq
 * @since 九月, 07 2021
 */
public class TimeWatch {

	private final String id;
	private List<TaskInfo> taskList;
	private String currentTaskName;
	private long startTimeNanos;
	private TaskInfo lastTaskInfo;
	private int taskCount;
	private long totalTimeNanos;

	public TimeWatch() {
		this("", true);
	}

	public TimeWatch(String id) {
		this(id, true);
	}

	public TimeWatch(String id, boolean keepTaskList) {
		this.id = id;
		if (keepTaskList) {
			this.taskList = new ArrayList();
		}

	}

	public String getId() {
		return this.id;
	}

	public void start() throws IllegalStateException {
		this.start("");
	}

	public void start(String taskName) throws IllegalStateException {
		if (null != this.currentTaskName) {
			throw new IllegalStateException("Can't start TimeWatch: it's already running");
		} else {
			this.currentTaskName = taskName;
			this.startTimeNanos = System.nanoTime();
		}
	}

	public void stop() throws IllegalStateException {
		if (null == this.currentTaskName) {
			throw new IllegalStateException("Can't stop TimeWatch: it's not running");
		} else {
			long lastTime = System.nanoTime() - this.startTimeNanos;
			this.totalTimeNanos += lastTime;
			this.lastTaskInfo = new TaskInfo(this.currentTaskName, lastTime);
			if (null != this.taskList) {
				this.taskList.add(this.lastTaskInfo);
			}

			++this.taskCount;
			this.currentTaskName = null;
		}
	}

	public boolean isRunning() {
		return this.currentTaskName != null;
	}

	public String currentTaskName() {
		return this.currentTaskName;
	}

	public long getLastTaskTimeNanos() throws IllegalStateException {
		if (this.lastTaskInfo == null) {
			throw new IllegalStateException("No tasks run: can't get last task interval");
		} else {
			return this.lastTaskInfo.getTimeNanos();
		}
	}

	public long getLastTaskTimeMillis() throws IllegalStateException {
		if (this.lastTaskInfo == null) {
			throw new IllegalStateException("No tasks run: can't get last task interval");
		} else {
			return this.lastTaskInfo.getTimeMillis();
		}
	}

	public String getLastTaskName() throws IllegalStateException {
		if (this.lastTaskInfo == null) {
			throw new IllegalStateException("No tasks run: can't get last task name");
		} else {
			return this.lastTaskInfo.getTaskName();
		}
	}

	public TaskInfo getLastTaskInfo() throws IllegalStateException {
		if (this.lastTaskInfo == null) {
			throw new IllegalStateException("No tasks run: can't get last task info");
		} else {
			return this.lastTaskInfo;
		}
	}

	public long getTotalTimeNanos() {
		return this.totalTimeNanos;
	}

	public long getTotalTimeMillis() {
		return DateUtil.nanosToMillis(this.totalTimeNanos);
	}

	public double getTotalTimeSeconds() {
		return DateUtil.nanosToSeconds(this.totalTimeNanos);
	}

	public int getTaskCount() {
		return this.taskCount;
	}

	public TaskInfo[] getTaskInfo() {
		if (null == this.taskList) {
			throw new UnsupportedOperationException("Task info is not being kept!");
		} else {
			return this.taskList.toArray(new TaskInfo[0]);
		}
	}

	public String shortSummary() {
		return StrUtil.format("TimeWatch '{}': running time = {} s", this.id, this.getTotalTimeSeconds());
	}

	public String prettyPrint() {
		StringBuilder sb = new StringBuilder(this.shortSummary());
		sb.append(FileUtil.getLineSeparator());
		if (null == this.taskList) {
			sb.append("No task info kept");
		} else {
			sb.append("---------------------------------------------").append(FileUtil.getLineSeparator());
			sb.append("seconds   %     Task name").append(FileUtil.getLineSeparator());
			sb.append("---------------------------------------------").append(FileUtil.getLineSeparator());
			NumberFormat nf = NumberFormat.getNumberInstance();
			nf.setMinimumIntegerDigits(2);
			nf.setGroupingUsed(false);
			NumberFormat pf = NumberFormat.getPercentInstance();
			pf.setMinimumIntegerDigits(3);
			pf.setGroupingUsed(false);
			TaskInfo[] var4 = this.getTaskInfo();
			for (TaskInfo task : var4) {
				String sec = nf.format(task.getTimeSeconds());
				sb.append(sec).append("          ".substring(sec.length()));
				sb.append(pf.format(task.getTimeSeconds() / this.getTotalTimeSeconds())).append("  ");
				sb.append(task.getTaskName()).append(FileUtil.getLineSeparator());
			}
		}

		return sb.toString();
	}

	public String toString() {
		StringBuilder sb = new StringBuilder(this.shortSummary());
		if (null != this.taskList) {
			for (TaskInfo task : this.taskList) {
				sb.append("; [").append(task.getTaskName()).append("] took ").append(task.getTimeNanos()).append(" ns");
				long percent = Math.round(100.0D * (double) task.getTimeNanos() / (double) this.getTotalTimeNanos());
				sb.append(" = ").append(percent).append("%");
			}
		} else {
			sb.append("; no task info kept");
		}

		return sb.toString();
	}

	public static final class TaskInfo {
		private final String taskName;
		private final long timeNanos;

		TaskInfo(String taskName, long timeNanos) {
			this.taskName = taskName;
			this.timeNanos = timeNanos;
		}

		public String getTaskName() {
			return this.taskName;
		}

		public long getTimeNanos() {
			return this.timeNanos;
		}

		public long getTimeMillis() {
			return DateUtil.nanosToMillis(this.timeNanos);
		}

		public double getTimeSeconds() {
			return DateUtil.nanosToSeconds(this.timeNanos);
		}
	}
}
